package com.fq.config;

import cn.hutool.core.util.StrUtil;
import com.fq.ability.shiro.MyShiroRealm;
import com.fq.ability.shiro.jwt.JwtFilter;
import com.fq.ability.shiro.properties.ShiroProperties;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
import org.apache.shiro.mgt.DefaultSubjectDAO;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * @Author 超chao
 * @Description Shiro配置类
 * @Date 2024/10/12/周六 15:15
 * @Version 1.0
 */
@Slf4j
@Configuration
public class ShiroConfig {
    private final static Integer EXPIRE_TIME = 20000;

    private final ShiroProperties shiroProperties;

    public ShiroConfig(ShiroProperties shiroProperties) {
        this.shiroProperties = shiroProperties;
    }

    /**
     * Filter Chain定义说明
     * <p>
     * 1、一个URL可以配置多个Filter，使用逗号分隔
     * 2、当设置多个过滤器时，全部验证通过，才视为通过
     * 3、部分过滤器可指定参数，如perms，roles
     */
    @Bean("shiroFilter")
    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        final String anon = "anon";
        final String jwt = "jwt";
        // 默认 anno map
        LinkedHashMap<String, String> allMap = Optional.ofNullable(shiroProperties.getAnon()).orElse(new ArrayList<>())
                .stream().collect(Collectors.toMap(map -> map, item -> anon, (key1, key2) -> key1, LinkedHashMap::new));
        // 获取自定义 jwt map
        LinkedHashMap<String, String> jwtMap = Optional.ofNullable(shiroProperties.getJwt()).orElse(new ArrayList<>())
                .stream().collect(Collectors.toMap(map -> map, item -> jwt, (key1, key2) -> key1, LinkedHashMap::new));
        // 添加自己的过滤器并且取名为 jwt
        shiroFilterFactoryBean.setFilters(new HashMap<>(1) {{
            put(jwt, new JwtFilter());
        }});
        // 添加 jwt 自定义路径配置
        allMap.putAll(jwtMap);
        shiroFilterFactoryBean.setFilterChainDefinitionMap(allMap);
        return shiroFilterFactoryBean;
    }


    /**
     * 配置SecurityManager，它是Shiro框架的核心安全管理器
     * 这个方法中配置了自定义的Realm、SubjectDAO、SessionStorageEvaluator和CacheManager
     *
     * @param myRealm 自定义的Realm，用于身份验证和授权
     * @return 返回配置好的DefaultWebSecurityManager实例
     */
    @Bean("securityManager")
    public DefaultWebSecurityManager securityManager(MyShiroRealm myRealm) {
        // 创建DefaultWebSecurityManager实例
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 设置自定义的Realm
        securityManager.setRealm(myRealm);

        // 创建SubjectDAO实例，用于管理Subject
        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
        // 创建SessionStorageEvaluator实例，用于控制是否启用会话存储
        DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
        // 禁用会话存储，因为Shiro的会话管理可能不适用于Web应用中的会话管理
        defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
        // 设置SubjectDAO的SessionStorageEvaluator
        subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
        // 设置SecurityManager的SubjectDAO
        securityManager.setSubjectDAO(subjectDAO);

        // 设置自定义的CacheManager，这里使用Redis作为缓存
        securityManager.setCacheManager(redisCacheManager());

        // 返回配置好的SecurityManager
        return securityManager;
    }

    /**
     * 下面的代码是添加注解支持
     *
     * @return
     */

    /**
     * 创建并配置 DefaultAdvisorAutoProxyCreator 实例
     * DefaultAdvisorAutoProxyCreator 是用于创建 AOP 代理类的关键组件
     * 通过配置该组件，可以控制代理类的生成方式以及命名规则等
     *
     * @return 配置好的 DefaultAdvisorAutoProxyCreator 实例
     */
    @Bean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        // 创建 DefaultAdvisorAutoProxyCreator 实例
        DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();

        // 设置是否生成代理类，而不是基于接口的代理，默认值为 false
        // 当设置为 true 时，即使没有接口，也会通过 CGLIB 生成代理类
        defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);

        // 设置是否使用前置代理， 默认值为 false
        // 当设置为 true 时，将生成 Jdk 动态代理的前置代理类
        defaultAdvisorAutoProxyCreator.setUsePrefix(true);

        // 返回配置好的 DefaultAdvisorAutoProxyCreator 实例
        return defaultAdvisorAutoProxyCreator;
    }

    @Bean
    public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }

    /**
     * cacheManager 缓存 redis实现
     * 使用的是shiro-redis开源插件
     *
     * @return
     */
    public RedisCacheManager redisCacheManager() {
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisManager(redisManager());
        //redis中针对不同用户缓存(此处的id需要对应user实体中的id字段,用于唯一标识)
        redisCacheManager.setPrincipalIdFieldName("id");
        //用户权限信息缓存时间
        redisCacheManager.setExpire(EXPIRE_TIME);
        return redisCacheManager;
    }

    /**
     * 配置shiro redisManager
     * 使用的是shiro-redis开源插件
     *
     * @return
     */
    @Bean
    public RedisManager redisManager() {
        ShiroProperties.RedisProperties redis = shiroProperties.getRedis();
        RedisManager redisManager = new RedisManager();
        redisManager.setHost(redis.getHost());
        redisManager.setDatabase(redis.getDatasource());
        if (StrUtil.isNotBlank(redis.getPassword())) {
            redisManager.setPassword(redis.getPassword());
        }
        return redisManager;
    }

}
